/*
 * EnemySpriteBase.cs
 *
 * Created on December 29, 2007, 12:32 PM
 *
 * Copyright (C) 2007-2008  Hylke van der Schaaf
 * Copyright (C) 2010       Mathijs Miermans
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, in version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

using System;
using System.Collections.Generic;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Controls;

using HexTD.sprite;
using HexTD.util;
using HexTD.wave;

public abstract class EnemySpriteBase : EnemySprite
{

	protected Size size = Size.normal;
	private string name;
	//
	protected bool inactive = true;
	protected bool isValidTarget = false;
	protected bool dead = false;
	protected int price;
	protected COLOR damageColor = COLOR.OTHER;
	protected COLOR damageColorAnti = COLOR.OTHER;
	private float delay;
	private float xFloat, yFloat;
	private float xPaint, yPaint;
	protected float paintScale;
	protected RotateTransform atTranslate;
	private HexTD.wave.Path path;
	private int segment = -1;
	private int segmentProgression = 0;
	private float[] segmentStartPoint;
	private float[] segmentEndPoint;
	private float progress = 0;
	protected float preferredDelay = 0.7f;
	protected float acceleration = 1;
	protected float speed = 50;          // * 1/1000 segments per tick
	protected float speedMax = 50;      // * 1/1000 segments per tick
	protected float speedMaxBase = 50;  // * 1/1000 segments per tick
	protected float speedRepairTime = 0;
	protected float power = 50;      // power > speedMax => speed = speedMax
	protected float powerBase = 50;  // power > speedMax => speed = speedMax
	protected float powerRepairTime = 0;
	protected long health;
	protected long healthMax;
	private Shape healthLineShape;
	private Brush healthLineStroke;
	private Color healthLineColor;
	private float shieldRotationStep = 0f;
	private float shieldW;
	private float shieldH;
	private int shieldO;
	private Color shieldColor = Color.FromArgb(255, 50, 50, 255);
	private Brush shieldStroke;
	protected bool shielded = false;
	private bool selected = false;
	//
	protected Context context;
	private List<EnemySpriteChangedListener> listeners;
	//
	public static string form = "#,###.#";

	/** Creates a new instance of EnemySpriteBase */
	public EnemySpriteBase(string name)
	{
		this.name = name;
	}

	protected virtual void doInit(Context context, int path, float delay, long health, int price, Size s)
	{
		this.context = context;
		this.price = price;
		this.health = health;
		this.healthMax = health;
		this.path = this.context.getPath(path);
		this.xFloat = 0;
		this.yFloat = 0;
		this.delay = delay;
		this.segmentProgression = (int)-Math.Round(this.delay * 1000);
		if (delay == 0)
		{
			this.inactive = false;
			this.isValidTarget = true;
		}
		this.healthLineColor = this.context.colors.getColorHealthLine();
		this.atTranslate = new RotateTransform();
		this.setSize(s);
		this.resetScale();
	}

	public virtual string getInfostring()
	{
		string sizeText = "";
		switch (this.size)
		{
			case Size.large:
				sizeText = "Boss!";
				break;
			case Size.normal:
				sizeText = "Normal";
				break;
			case Size.small:
				sizeText = "Swarm!";
				break;
		}
		return "<b>Health:</b> " + this.healthMax.ToString(form) + "<br>" +
				"<b>Size:</b> " + sizeText + "<br>" +
				"<b>Reward:</b> " + this.price + "€";
	}

	public string getStatusstring()
	{
		string sizeText = "";
		switch (this.size)
		{
			case Size.large:
				sizeText = "Boss!";
				break;
			case Size.normal:
				sizeText = "Normal";
				break;
			case Size.small:
				sizeText = "Swarm!";
				break;
		}
		return "<b>Type:</b> " + this.name + "<br>" +
				"<b>Size:</b> " + sizeText + "<br>" +
				"<b>Health:</b> " + this.health.ToString(form) + "<br>" +
				"<b>Speed:</b> " + speed + "<br>" +
				"<b>Reward:</b> " + this.price + "€";
	}

	public long getHealth()
	{
		return this.health;
	}

	public float getProgress()
	{
		return this.progress;
	}

	public void setSize(Size s)
	{
		this.size = s;
	}

	public void setSelected(bool selected)
	{
		this.selected = selected;
	}

	public void remove()
	{
		this.health = 0;
		this.dead = true;
		this.context.removeEnemy();
	}

	public virtual void doDamage(float damage, DamageType t, COLOR c)
	{
		float realDamage = damage;
		switch (t)
		{
			case DamageType.normal:
				doDamage((int)Math.Round(damage), c);
				break;
			case DamageType.speed:
				if (c == COLOR.OTHER)
				{
					realDamage = damage;
				}
				else if (c == this.damageColor)
				{
					realDamage = damage * 0.5f;
				}
				else if (c == this.damageColorAnti)
				{
					realDamage = damage * 2;
				}
				if (this.speedMax > this.speedMaxBase * realDamage + 0.01)
				{
					this.speedMax = this.speedMaxBase * realDamage;
					this.speedRepairTime = 1f;
					//System.Diagnostics.Debug.WriteLine("Reducing speed to "+this.speedMax);
				}
				else
				{
					this.speedRepairTime += 1f;
					//System.Diagnostics.Debug.WriteLine("Increasing speed-repairtime to "+this.speedRepairTime);
				}
				this.fireStatusChangedEvent();
				break;
			case DamageType.power:
				if (c == COLOR.OTHER)
				{
					realDamage = damage;
				}
				else if (c == this.damageColor)
				{
					realDamage = damage * 0.5f;
				}
				else if (c == this.damageColorAnti)
				{
					realDamage = damage * 2;
				}
				if (this.power > this.powerBase * realDamage + 0.01)
				{
					this.power = this.powerBase * realDamage;
					this.powerRepairTime = 1f;
					//System.Diagnostics.Debug.WriteLine("Reducing power to "+this.power);
				}
				else
				{
					this.powerRepairTime += 1f;
					//System.Diagnostics.Debug.WriteLine("Increasing power-repairtime to "+this.powerRepairTime);
				}
				this.fireStatusChangedEvent();
				break;
			case DamageType.time:
				if (c == COLOR.OTHER)
				{
					realDamage = damage;
				}
				else if (c == this.damageColorAnti)
				{
					realDamage = damage * 0.5f;
				}
				else if (c == this.damageColor)
				{
					realDamage = damage * 2;
				}

				int segmentDif = (int)Math.Floor(realDamage);
				int segmentPartDif = (int)Math.Round(1000 * (realDamage - segmentDif));
				this.segment -= segmentDif;
				this.segmentProgression -= segmentPartDif;
				//System.Diagnostics.Debug.WriteLine("Rewinding with  "+segmentDif+", "+segmentPartDif);
				while (this.segmentProgression < 0)
				{
					this.segmentProgression += 1000;
					this.segment--;
				}
				if (this.segment < 0)
				{
					this.segment = 0;
				}
				this.resetPosition();
				this.shielded = true;
				break;
		}
	}

	public virtual void doDamage(int damage, COLOR c)
	{
#if PORT
		if (c == COLOR.OTHER) {
			this.doDamage(damage);
		} else if (c == this.damageColor) {
			this.doDamage(Math.Round(damage * 2f));
		} else if (c == this.damageColorAnti) {
			this.doDamage(Math.Round(damage * 0.25f));
		} else {
			this.doDamage(damage);
		}
#endif
	}

	protected virtual void doDamage(int damage)
	{
		if (this.validTarget())
		{
			this.health -= damage;
		}
		if (this.health <= 0)
		{
			this.isValidTarget = false;
			this.dead = true;
			this.context.addScore(this.price);
			this.context.doReceive(this.price);
			this.context.removeEnemy();
		}
		this.fireStatusChangedEvent();
	}

	public float getX()
	{
		return this.xFloat;
	}

	public float getY()
	{
		return this.yFloat;
	}

	public float getPaintX()
	{
		return this.xPaint;
	}

	public float getPaintY()
	{
		return this.yPaint;
	}

	public float getSpeed()
	{
		return this.speed;
	}

	public float getPreferredDelay()
	{
		return this.preferredDelay;
	}

	public bool validTarget()
	{
		return ((!this.inactive) && this.isValidTarget && !this.dead);
	}

	public bool isDead()
	{
		return this.dead;
	}

	public bool isShielded()
	{
		return this.shielded;
	}

	private void resetPosition()
	{
#if PORT
		this.segmentStartPoint = this.path.getStep(this.segment);
		this.segmentEndPoint = this.path.getStep(this.segment + 1);
		this.xFloat = this.segmentStartPoint[0] + (this.segmentEndPoint[0] - this.segmentStartPoint[0]) * this.segmentProgression / 1000f;
		this.yFloat = this.segmentStartPoint[1] + (this.segmentEndPoint[1] - this.segmentStartPoint[1]) * this.segmentProgression / 1000f;
		this.xPaint = this.xFloat * this.paintScale + this.context.offsetX;
		this.yPaint = this.yFloat * this.paintScale + this.context.offsetY;
#endif
	}

	public virtual void doTick(int gameTime)
	{
#if PORT
		if (this.inactive) {
			// not started yet.
			if (this.delay > 0 && this.segmentProgression < 0) {
				this.segmentProgression += this.speed;
				if (this.segmentProgression >= 0) {
					this.inactive = false;
					this.isValidTarget = true;
					this.doTick(gameTime);
				}
			}
		} else if (this.dead) {
		} else {

			// Speed and power
			if (this.powerRepairTime > 0) {
				this.powerRepairTime -= 0.0125f;
				if (this.powerRepairTime <= 0) {
					this.power = this.powerBase;
				}
			}
			if (this.speedRepairTime > 0) {
				this.speedRepairTime -= 0.5f;
				if (this.speedRepairTime <= 0) {
					this.speedMax = this.speedMaxBase;
				}
			}
			float powerRatio = Math.Min(1f, this.power / this.speedMaxBase);
			float speedMaxCurrent = this.speedMax * powerRatio;
			if (this.speed < speedMaxCurrent) {
				this.speed += this.acceleration;
			}
			if (this.speed > speedMaxCurrent) {
				this.speed = speedMaxCurrent;
			}
			if (this.speed < 0.05f * this.speedMaxBase) {
				this.speed = 0.05f * this.speedMaxBase;
			}

			// Healthbar
			float healthRatio = 1.0f * this.health / this.healthMax;
			float paintScale = this.context.paintScale;
			this.healthLineShape = new LineSegment.Float(-0.3f * paintScale * healthRatio, -0.3f * paintScale, 0.3f * paintScale * healthRatio, -0.3f * paintScale);

			this.segmentProgression += this.speed;
			if (this.segmentProgression >= 1000) {
				this.segmentProgression -= 1000;
				this.segment++;
				if (this.segment >= this.path.length()) {
					this.segment = -1;
					this.context.removeLife();
				}
				this.segmentStartPoint = this.path.getStep(this.segment);
				this.segmentEndPoint = this.path.getStep(this.segment + 1);
			}
			this.xFloat = this.segmentStartPoint[0] + (this.segmentEndPoint[0] - this.segmentStartPoint[0]) * this.segmentProgression / 1000f;
			this.yFloat = this.segmentStartPoint[1] + (this.segmentEndPoint[1] - this.segmentStartPoint[1]) * this.segmentProgression / 1000f;
			this.xPaint = this.xFloat * paintScale + this.context.offsetX;
			this.yPaint = this.yFloat * paintScale + this.context.offsetY;
			atTranslate.setToIdentity();
			atTranslate.translate(this.xPaint, this.yPaint);
			if (this.xFloat < this.context.minX || this.xFloat > this.context.maxX || this.yFloat < this.context.minY || this.yFloat > this.context.maxY) {
				this.isValidTarget = false;
			} else {
				this.isValidTarget = true;
			}

			this.progress = 1.0f * this.segment / this.path.length() + 1.0f * this.segmentProgression / (this.path.length() * 1000);
			if (this.shielded) {
				this.progress++;
			}
		}
#endif
	}

	public virtual void resetScale()
	{
#if PORT
		this.paintScale = this.context.paintScale;
		this.shieldStroke = new Brush(2.0f * this.context.strokeScale, Brush.CAP_ROUND, Brush.JOIN_BEVEL);
		this.healthLineStroke = new Brush(3.0f * this.context.strokeScale, Brush.CAP_BUTT, Brush.JOIN_BEVEL);

		this.resetPosition();
#endif
	}

	public abstract void paint(Canvas g2, int gameTime);

	public void paintHealthLine(Canvas g2)
	{
#if PORT
		if (this.health > 0 && this.healthLineShape != null) {
			g2.setStroke(this.healthLineStroke);
			g2.setColor(this.healthLineColor);
			g2.draw(this.healthLineShape);
		}
#endif
	}

	public void paintShield_bottom(Canvas g2)
	{
#if PORT
		this.shieldRotationStep += 0.1;
		this.shieldH = 0.4f * this.context.paintScale;
		this.shieldW = this.shieldH * (float) Math.Sin(this.shieldRotationStep);
		float front = (float) Math.Cos(this.shieldRotationStep);
		if ((this.shieldW < 0 && front < 0) || (this.shieldW > 0 && front > 0)) {
			this.shieldO = -1;
		} else {
			this.shieldO = 1;
		}
		this.shieldW = Math.Abs(this.shieldW);
		Shape bottom = new Arc2D.Double(-this.shieldW, -this.shieldH,
				this.shieldW * 2,
				this.shieldH * 2,
				this.shieldO * 90, 180,
				Arc2D.OPEN);
		g2.setStroke(this.shieldStroke);
		g2.setColor(this.shieldColor);
		g2.draw(bottom);
#endif
	}

	public void paintShield_top(Canvas g2)
	{
#if PORT
		Shape top = new Arc2D.Double(-this.shieldW, -this.shieldH,
				this.shieldW * 2,
				this.shieldH * 2,
				-this.shieldO * 90, 180,
				Arc2D.OPEN);
		g2.setStroke(this.shieldStroke);
		g2.setColor(this.shieldColor);
		g2.draw(top);
#endif
	}

	public void paintSelected(Canvas g2)
	{
#if PORT
		if (this.selected) {
			g2.setColor(Colors.LightGray);
			g2.setStroke(shieldStroke);
			g2.draw(new Ellipse.Float(this.getPaintX() - 0.5f * paintScale, this.getPaintY() - 0.5f * paintScale, paintScale, paintScale));
		}
#endif
	}

	protected void fireStatusChangedEvent()
	{
#if PORT
		if (this.listeners != null) {
			try {
				foreach (EnemySpriteChangedListener l in this.listeners) {
					l.enemySpriteStatusChanged();
				}
			} catch (ConcurrentModificationException e) {
			}
		}
#endif
	}

	public void addStatusListener(EnemySpriteChangedListener l)
	{
		if (this.listeners == null)
		{
			this.listeners = new List<EnemySpriteChangedListener>();
		}
		this.listeners.Add(l);
	}

	public void removeStatusListener(EnemySpriteChangedListener l)
	{
		if (this.listeners != null)
		{
			this.listeners.Remove(l);
		}
	}

	public EnemySprite create(Context context, int path, float delay, long health, int price, Size s)
	{
		EnemySpriteBase newEnemy = null;
#if PORT
		try {
			newEnemy = (EnemySpriteBase) this.clone();
		} catch (CloneNotSupportedException ex) {
			ex.printStackTrace();
		}
		newEnemy.doInit(context, path, delay, health, price, s);
#endif
		return newEnemy;
	}
}
